using System;
using System.ServiceModel;
using System.Collections.Generic;

namespace Ayatta.Common
{
    public class ServiceClientProxy<TChannel> : ServiceClientProxy
    {
        public string EndpointConfigurationName { get; private set; }

        public ServiceClientProxy(string endpointConfigurationName)
        {
            EndpointConfigurationName = endpointConfigurationName;
        }

        public void Invoke(Action<TChannel> action)
        {
            Invoke(action, EndpointConfigurationName);
        }

        public TResult Invoke<TResult>(Func<TChannel, TResult> function)
        {
            return Invoke(function, EndpointConfigurationName);
        }
        
    }
    public abstract class ServiceClientProxy
    {
        private static readonly object SyncHelper = new object();
        private static readonly Dictionary<string, ChannelFactory> ChannelFactories = new Dictionary<string, ChannelFactory>();

        private static ChannelFactory<TChannel> GetChannelFactory<TChannel>(string endpointConfigurationName)
        {
            ChannelFactory<TChannel> channelFactory = null;
            if (ChannelFactories.ContainsKey(endpointConfigurationName))
            {
                channelFactory = ChannelFactories[endpointConfigurationName] as ChannelFactory<TChannel>;
            }
            if (null == channelFactory)
            {
                channelFactory = new ChannelFactory<TChannel>(endpointConfigurationName);

                lock (SyncHelper)
                {
                    ChannelFactories[endpointConfigurationName] = channelFactory;
                }
            }
            return channelFactory;
        }

        protected static void Invoke<TChannel>(Action<TChannel> action, TChannel proxy)
        {
            var channel = proxy as ICommunicationObject;
            if (null == channel)
            {
                throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy");
            }
            try
            {
                action(proxy);
            }
            catch (TimeoutException)
            {
                channel.Abort();
                throw;
            }
            catch (CommunicationException)
            {
                channel.Abort();
                throw;
            }
            finally
            {
                channel.Close();
            }
        }

        protected static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, TChannel proxy)
        {
            var channel = proxy as ICommunicationObject;

            if (null == channel)
            {
                throw new ArgumentException("The proxy is not a valid channel implementing the ICommunicationObject interface", "proxy");
            }
            try
            {
                return function(proxy);
            }
            catch (TimeoutException)
            {
                channel.Abort();
                throw;
            }
            catch (CommunicationException)
            {
                channel.Abort();
                throw;
            }
            finally
            {
                channel.Close();
            }
        }

        protected static void Invoke<TChannel>(Action<TChannel> action, string endpointConfigurationName)
        {
            Invoke(action, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());
        }

        protected static TResult Invoke<TChannel, TResult>(Func<TChannel, TResult> function, string endpointConfigurationName)
        {
            return Invoke(function, GetChannelFactory<TChannel>(endpointConfigurationName).CreateChannel());
        }
    }
}
